﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Dynamic;
using System.Text;
using System.Xml;
using System.Collections.Concurrent;
using System.Runtime.Serialization;
using System.IO;
using System.ComponentModel;
using System.Threading;
using u = FinPlusUtility.Utility;
using log = Logger.Logger;

namespace FinPlusCommon
{
    public enum ChangeType { New, Removed, Altered };

    public class Functions : ConcurrentDictionary<int, IFinPlusFunction>, IFinPlusFunctions
    {
        public new IFinPlusFunction this[int Id] { get { return base[Id]; } set { Add(value, false, false); } }
        public string[] DisplayFields { get { return _displayFields; } set { _displayFields = value; } }
        public string[] DisplayOrder { get { return _displayOrder; } set { _displayOrder = value; } }
        public EventHandler<ItemEventArgs> FuncUpdate { get; set; }
        public string Guid { get; set; }

        private enum Params { TimeStamp };
        private string[] _displayFields = new[] { "Id" }, _displayOrder = new[] { "Id" };
        private string _xmlNodes = "root/current";
         
        //construct
        public Functions() 
        {
            Guid = u.Guid();
        }

        public Functions(string xmlPathOrString, string xmlNodes, string replaceValue = "")
        {
            try
            {
                _xmlNodes = xmlNodes;
                var replace = u.ToDictionary(replaceValue, true, ';',',', string.Empty);

                var doc = new XmlDocument();
                if(xmlPathOrString.Contains('<'))
                    doc.Load(new StringReader(xmlPathOrString));
                else
                    doc.Load(xmlPathOrString);

                var id = 1;
                foreach (XmlElement xmlFuncs in doc.SelectNodes(xmlNodes))
                    Add(ref id, xmlFuncs, replace);
            }
            catch
            {
                throw;
            }
        }

        //public
        public IFinPlusFunctions Clone()
        {
            var clone = new Functions();
            
            lock (this)
                foreach (var func in this.Values)
                    clone.Add(func.Clone(func.Id));

            return clone;
        }

        public void Refresh()
        {
            foreach (var func in this.Values)
                func.Refresh();
        }

        public void Add(IFinPlusFunctions funcs, bool nextId = true, bool setId = true)
        {
            foreach (var func in funcs.Values)
                this.Add(func, nextId, setId);
        }

        public void Add(ref int id, XmlElement xmlFuncs, IDictionary<string, string> replace = null)
        {
            foreach (XmlElement xmlFunc in xmlFuncs)
            {
                var func = new Function(id, xmlFunc, replace);
                this.TryAdd(id, func);
                id++;

                foreach(XmlElement xmlChildFuncs in xmlFunc)
                    if (xmlChildFuncs.Name.Equals("Dock"))
                        Add(ref id, xmlChildFuncs, replace);
            }
        }

        public IFinPlusFunction Add(IFinPlusFunction func, bool nextId = true, bool setId = true)
        {
            func.ItemUpdate += Func_Update;

            if (nextId)
              func.Id = func.Id <= 0 ? NextKey() : func.Id;
            
            if (setId)
               func.Get("Id", func.Id).Set(func.Id);

            base.TryAdd(func.Id, func);

            return func;
        }

        public void Get<T>(string name, T defaultValue, PropertyChangedEventHandler changeEventHander = null)
        {
            foreach (var func in Values)
                func.Get(name, defaultValue, changeEventHander);
        }

        public void Set<T>(string name, string value, T defaultValue)
        {
            foreach (var func in Values)
                func.Get(name, defaultValue).Set(value);
        }

        public void Update(IFinPlusFunction func, ChangeType changeType)
        {
            func.Id = (int)u.IntParse(func.Get("Id").Value, -1);

            IFinPlusFunction funcUpdate = null;

            if (changeType == ChangeType.Removed && TryRemove(func.Id, out funcUpdate) && FuncUpdate != null)
                FuncUpdate.Invoke((object)func, new ItemEventArgs(null, changeType));
            else if (changeType == ChangeType.New && Add(func) != null && FuncUpdate != null)
                FuncUpdate.Invoke((object)func, new ItemEventArgs(null, changeType));
            else if (changeType == ChangeType.Altered && TryGetValue(func.Id, out funcUpdate))
                funcUpdate.ItemsUpdate(func);
        }

        public void Remove(string expression)
        {
            try
            {
                lock (this)
                {
                    IFinPlusFunction f = null;
                    var funcIds = (from func in this.Values where func.IsMatch(expression) == false select func.Id).ToArray();
                    if (funcIds.Any())
                        foreach (var funcId in funcIds)
                            this.TryRemove(funcId, out f);
                }
            }
            catch
            {
                
            }
        }

        public new bool TryRemove(int functionId, out IFinPlusFunction func)
        {
            return base.TryRemove(functionId, out func);
        }

        public IFinPlusFunction First(string expression)
        {
            try
            {
                var res = this.Values.Where(f => f.IsMatch(expression));
                if(res.Count() <1 ) return null;
                return res.First();
            }
            catch
            {
                return null;
            }
        }

        public IDictionary<string, IFinPlusFunction> Where(string expression, string key = "Id")
        {
            try
            {
                return this.Values.Where(f => f.IsMatch(expression)).ToDictionary(k => k[key].ToString(), f => f);
            }
            catch
            {
                return null;
            }
        }

        public XmlDocument ToXmlDoc(string nodes = "")
        {
            var xmlDoc = new XmlDocument();
            try
            {
                if (string.IsNullOrEmpty(nodes))
                    nodes = _xmlNodes;

                XmlNode xmlNode = null, xmlNodePrev = null; 

                foreach(var node in nodes.Trim('/').Split('/'))
                {
                    xmlNode = xmlDoc.CreateNode(XmlNodeType.Element, node, null);
                    if (xmlNodePrev == null)
                        xmlDoc.AppendChild(xmlNode);
                    else
                        xmlNodePrev.AppendChild(xmlNode);
                    xmlNodePrev = xmlNode;
                }

                foreach(var func in this.Values)
                    func.ToXmlDoc(xmlDoc, nodes);

            }
            catch
            {
                throw;
            }

            return xmlDoc;
        }

        public override string ToString()
        {
            return ToString(FieldType.Value);
        }

        public string ToString(string node = "")
        {
            var xmlDoc = ToXmlDoc(node);

            var sw = new StringWriter();
            var tx = new XmlTextWriter(sw);
            tx.Formatting = Formatting.Indented;
            xmlDoc.WriteTo(tx);
            return sw.ToString();
        }

        public string ToString(FieldType fieldType)
        {

            //get list nvp key as per sort and func as string for selected field
            var res1 = this.Values.Select(f => (new KeyValuePair<string, string>(f.SortKey(DisplayOrder), f.ToString(DisplayFields)))).ToList();

            //sort function strings via key then to string
            var res2 = string.Join("%", res1.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value).ToArray());

            return res2;
        }

        public IFinPlusFunction[] ToArray(string name, string match)
        {
            return Values.Where(f => f[name].ToString().Equals(match)).ToArray<IFinPlusFunction>();
        }

        public IList<IFinPlusFunction> ToList(string name, string match, bool isValMatch = true)
        {
            if(isValMatch)
                return Values.Where(f => f.Get(name).Value.Equals(match)).ToList();

            return Values.Where(f => f.Get(name).Name.Equals(match)).ToList();

        }

        public IList<IFinPlusFunction> ToList(string name, string match, char delimitor)
        {
            return Values.Where(f => f[name].ToString().Split(delimitor).ToList().Contains(match)).ToList();
        }

        public IList<string> ToList(string name)
        {
            return Values.Where(f => f.Name.Equals(name)).Select(f => f[name].ToString()).ToList<string>();
        }

        public IDictionary<string, string> ToDictionary(string key, string value)
        {
            return Values.ToDictionary(f => f[key].ToString(), f => f[value].ToString());
        }

        public static IFinPlusFunctions ParseFunctions(string xmlPathOrString, string node, string parms = "")
        {
            return new Functions(xmlPathOrString, node, parms);
        }

        public int NextKey()
        {
            return this.Count() < 1 ? 0 : this.Max(kvp => kvp.Key) + 1;
        }

         //events
        private void Func_Update(object sender, ItemEventArgs a)
        {
            if (FuncUpdate != null)
                FuncUpdate.Invoke(sender, a);
        }
    }
}




































































































































//Copyright2013<jnrton>AllRightsReserved 
//Contact j.nrton@finpus